package mygame.hudelements;

import com.jme3.asset.AssetManager;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.ui.Picture;
import mygame.Main;

/**
 * This class simulates an altimeter of a plane. It is designed as a
 * controll wich can be attached directly to the central plane node. It needs access
 * to the spatial translation vector (y value)
 * @author Aicher
 * @version 0.0.1
 */
public class AltimeterControl extends InstrumentControl {
    /**
     * Highest kick (amplitude) of the vario indicator arrow (usually a bit
     * higher than the MAX_VARIO_VALUE.
     */
    private static final float MAX_VARIO_INDICATOR_VALUE = 17;
       
    /**
     * Highest number of vario indicator on the scale
     */
    private static final float MAX_VARIO_VALUE = 15;
    
    /**
     * File names of the three indicators
     */
    public static final String IMG_ALT_IND_FILENAMES[] = {
        "Textures/altimeter/alt_arrow0.png",
        "Textures/altimeter/alt_arrow1.png",
        "Textures/altimeter/alt_arrow2.png"
    };

    /**
     * The instrument frame image name
     */
    public static final String IMG_ALTIMETER_INSTRUMENT = "Alt_Instrument";
    
    /**
     * The variometer arrow image name
     */
    public static final String IMG_ALTIMETER_IND_VARIO = "Alt_Ind_Vario";
    
    /**
     * Name of indicators. They will be extended by the index of the arrow:
     * Alt_Indicator_
     */    
    public static final String IMG_ALT_IND = "Alt_Indicator_";
    
    /**
     * Altitude in feet
     */
    private float altitudeInFeet = 0f;
    
    /**
     * Angle, the arrow must be rotated
     */
    private float arrowAngle = 0f;
    
    /**
     * Dimension factor for calculating the indicator angle
     */
    private float angleDimmension = 1000;
    
    /**
     * This vector will be rotated and used as a reference for all arrows.
     */
    private Vector3f rotationVector = new Vector3f(0f, 0f, 0f);
    
    
    /**
     * This rotates the rotation vector
     */
    private Quaternion rotationQuat = new Quaternion();
    
    /**
     * The Arrows will rotate around this points
     */    
    public Node[] indicatorRotationPoint = new Node[IMG_ALT_IND_FILENAMES.length];
    
    /**
     * Altitude in meter of the current cycle (for change calcuations)
     */
    private float currentAltitude = 0f;
    
    /**
     * Altitude in meter of the last cycle (for change calcuations)
     */
    private float lastAltitude = 0f;
    
    /**
     * Current change speed of the altitude in compared to last cycle
     */
    private float currentAltitudeChangeSpeed = 0f;
        
    /**
     * variometer indicator image
     */
    private Picture varioIndicator = null;
        
    /**
     * Factor of how far the vario indicator must be moved based on the 
     * altitude change value
     */
    private float varioMovementFactor = 0f;
        
    

    /**
     * Constructor
     *
     * @param guiNode Reference to the guiNode
     * @param assetManager For loading the images
     * @param instrumentLeftGridPosX position of the left border (in grid size
     * measures)
     * @param instrumentLowerGridPos position of the lower border (in grid size
     * measures)Y
     * @param hudReferenceSize Sise of one grid measure
     * @param instrumentGridWidth Width of the instrument in grid measures
     * @param instrumentGridHeight Height of the instrument in grid measures
     * @param screenHeight Height of the screen in pixel
     * @param measureFromBottom Set to true, if the LowerGridPos should be
     * measured from the bottom. Set it to fals in order to measure it from the
     * top
     */
    public AltimeterControl (Node guiNode,
            AssetManager assetManager,
            float instrumentLeftGridPosX,
            float instrumentLowerGridPosY,
            float hudReferenceSize,
            float instrumentGridWidth,
            float instrumentGridHeight,
            int screenHeight,
            boolean measureFromBottom,
            float minimalCalculationLoopTime
            ) {
        super(guiNode, assetManager,
                instrumentLeftGridPosX,
                instrumentLowerGridPosY,
                hudReferenceSize,
                instrumentGridWidth,
                instrumentGridHeight,
                screenHeight,
                measureFromBottom,
                minimalCalculationLoopTime);
        initAltimeter();
    }

    /**
     * Initialisation of the altimeter
     */
    private void initAltimeter() {
        // Place the instrument panel
        this.centralInstrumentNode.attachChild(
                this.createInstrumentElementAbsolute(IMG_ALTIMETER_INSTRUMENT, 
                "Textures/altimeter/alt_instrument.png"));

                // place the vario indicator
        this.varioIndicator = this.createInstrumentElementAbsolute(
                IMG_ALTIMETER_IND_VARIO, "Textures/altimeter/alt_vario.png");
        this.centralInstrumentNode.attachChild(this.varioIndicator);
        this.varioMovementFactor = (130/700.0f) * this.instrumentHeight / MAX_VARIO_VALUE;
        
        // Now place all indicators by creating a central rotation point
        // and attach the three indicators to them
        for (int i = 0; i < this.indicatorRotationPoint.length; i++) {
            this.indicatorRotationPoint[i] = new Node();
            this.indicatorRotationPoint[i].setLocalTranslation(
                    this.instrumentCenterPosX, this.instrumentCenterPosY, 0);
            this.indicatorRotationPoint[i].attachChild(this.createInstrumentElementRelativeCenter(
                    IMG_ALT_IND + i, IMG_ALT_IND_FILENAMES[i]));
            this.centralInstrumentNode.attachChild(this.indicatorRotationPoint[i]);
        }
    }
    
    
    /**
     * Calculates the vario value (altidute gain/loss per second in meter)
     * @param cycleTime Time per call of this method (attention not equal to tpf
     * because of the variable calculation time of the hud controls)
     */
    private void moveVarioIndicator(float cycleTime) {
        // calculate delta value by storing the last altitude and the current altitude. 
        this.lastAltitude = this.currentAltitude;
        this.currentAltitude = this.spatial.getLocalTranslation().y;
        
        // the change speed is the delta divided by the time between the last and current altitude value
        this.currentAltitudeChangeSpeed = (this.currentAltitude - this.lastAltitude) / cycleTime;
        
        // Now move the arrow image according to the change value. The value is limited by
        // the maximum indicator kick (MAX_VARIO_INDICATOR_VALUE)
        this.varioIndicator.setLocalTranslation(this.instrumentLeftPosX, 
                this.instrumentLowerPosY + FastMath.clamp(this.currentAltitudeChangeSpeed, 
                -MAX_VARIO_INDICATOR_VALUE, MAX_VARIO_INDICATOR_VALUE) * this.varioMovementFactor, 0f);
    }

    /**
     * Perform the rotation of the three indicators
     */
    private void rotateIndicators() {
        // First get the altitude and convert it to "feet"
        this.altitudeInFeet = this.spatial.getLocalTranslation().y * Main.COORD_IN_FEET_FACTOR;
        
        // The first arrow is able to show 1000 feet (by one complete round)
        this.angleDimmension = 1000;
        for (int i = 0; i < this.indicatorRotationPoint.length; i++) {
            
            // This vector will be the reference for the picture rotation
            this.rotationVector.set(0f, 1f, 0f);
            // Calculate the angle - first calculate the fragment of one complete round and then multiply it by
            // 2Pi. With the modulo operation we get only the fraction of 2Pi
            this.arrowAngle = -(FastMath.TWO_PI * (this.altitudeInFeet / this.angleDimmension)) % FastMath.TWO_PI;
            
            // rotate the reference vector with a quaternion
            this.rotationQuat.fromAngleNormalAxis(this.arrowAngle, Vector3f.UNIT_Z);
            this.rotationQuat.multLocal(this.rotationVector);
            
            // rotate the indicator towards the reference vector
            this.indicatorRotationPoint[i].rotateUpTo(this.rotationVector);
            
            // The next indicator shows by factor 10 more feet
            this.angleDimmension *= 10;
        }
    }
    
    @Override
    protected void controlUpdate(float tpf) {
        // summarize the the tpf values and check when the stored calculationLoopTime is reached
        this.timeSinceLastUpdate += tpf;
        if (this.timeSinceLastUpdate > this.minimalCalculationLoopTime) {
            this.moveVarioIndicator(this.timeSinceLastUpdate);
            this.rotateIndicators();
            this.timeSinceLastUpdate = 0f;
        }
    }

    @Override
    public Control cloneForSpatial(Spatial theSpatial) {
        final AltimeterControl control = new AltimeterControl(
                guiNode, 
                assetManager,
                instrumentLeftGridPosX,
                instrumentLowerGridPosY,
                hudReferenceSize,
                instrumentGridWidth,
                instrumentGridHeight,
                screenHeight,
                measureFromBottom,
                minimalCalculationLoopTime);
        
        control.setSpatial(spatial);
        return control;
    }    
    
    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        // not needed yet
    }
    
}